桥接
实现 API 的时候,桥接模式非常有用,可能正是由于这个,该模式使用地不够广泛.在设计 js API 时,该模式可以弱化API 与使用它的类和对象之间的耦合.
该模式的作用在于**将抽象与其实现隔离开,让他们独立变化**.而且对于事件驱动编程有许多好处.
js API有 getter, setter, requester 以及其他基于动作的方法.无论它们是用来创建 Web 鼓舞 API 还是普通 accessor 和 mutator.在实现过程中桥接模式都有助于保持 API 代码的简洁.
事件监听器
最常见的应用场合之一是事件监听回调.假设有一个 API 函数 getBeerById, 他根据一个标识符返回有关啤酒的信息.
addEvent(ele, 'click', getBeerById);
function getBeerById(e) {
var id = this.id;
asyncRequest('GET', 'beer.url?id=' + id, function(resp) {
// Callback response.
console.log('Requested Beer: ' + resp.responseText);
});
}
这个 API 只能工作在浏览器中,根据事件监听器回调函数的机制,事件对象自然会被作为第一个参数传递给这个函数,在本例中没有使用这个参数,只是从 this 对象获取 id.那么如果在命令行环境运行它就失效不起作用.
function getBeerById(id, callback) {
// Make request for beer by ID, then return the beer data.
asyncRequest('GET', 'beer.url?id=' + id, function (resp) {
// callback reponse
callback(resp.responseText);
});
}
addEvent(ele, 'click', getBeerByIdBridge);
function getBeerByIdBridge(e) {
getBeerById(this.id, function (beer) {
console.log('Requested Beer: ' + beer);
});
}
这样就只针对接口而不是实现进行编程,用桥接模式把抽象隔离开来.
有了这层桥接元素,API 的适用范围大大拓宽,现在的 getBeerById 没有和事件对象绑定在一起,可以在单元测试中运行该 API,只需提供一个 ID 和对调函数.现在可以在命令行环境运行这个接口.
其他例子
除了在事件回调函数与接口之间进行桥接外,桥接模式还可以用连接公开的 API代码和私有的实现代码.另外,还可以连接多个类.从类的角度看,意味着把接口作为公开的代码编写,把类的实现作为私有代码编写.
可以使用一些特权方法来做桥梁以便访问私有变量空间.第3章已经见到过.
var Public = function () {
var secretNum = 3;
this.privilegedGetter = function () {
return secretNum;
};
};
var p = new Public();
var data = p.privilegedGetter();
连接多个类:
var Class1 = function (a, b, c) {
this.a = a;
this.b = b;
this.c = c;
};
var Class2 = function (d) {
this.d = d;
}
var BridgeClass = function (a, b, c, d) {
this.one = new Class1(a, b, c);
this.two = new Class2(d);
}
在这里,桥接类是一个门面类,不同的是桥接模式能够让Class1和 Class2独立于 BridgeClass 而发生变化,和门面类不同.
适用场合
事件驱动编程必备.不过我们喜欢事件驱动开发的函数式风格,却忘了编写接口.判断什么时候是用桥接模式很简单:
$('#example-id').on('click', function () {
new RichTextEditor();
})l
现在没法看出编辑器要显示在哪,有什么配置项,还有如何修改.要做的是让接口'可桥接'(可适配)...
还有就是之前讲到的特权函数用来访问私有数据.
利
让 API 更强壮,提高组件的模块化程度.把抽象和其实现隔离开,有助于独立管理,而且Bug 更容易查找.
弊
每使用一个桥接元素都要增加一次函数调用,提高系统复杂度,影响性能,不要滥用,举例来说,如果一个桥接函数被用于连接两个函数,但是其中某个函数根本不会在桥接函数之外被调用,那么该桥接函数可以不用.
----------next part----------
组合
同一条命令在多个对象上引发复杂或者递归的操作.
两大好处:
用同样的方法处理对象集合和其中的特定子对象.组合对象 composite 和组成它的对象实现了同批操作.是一种向下传递的操作性质.
把一批子对象组织成树形结构,使整棵树都可以被遍历.所有组合对象都实现了一个用来获取子对象的方法.
组合对象的结构
组合对象的层次体系中有两种类型的对象:叶对象和组合对象,是一个递归定义:一个组合对象由一些别的组合对象和叶对象组成.只有叶对象不再包含子对象,它是组合对象中最基本的元素,也落实了各种操作.(树是数据结构中较为基本的概念,这里的概念应该不难理解的)
使用
必须同时具备两个条件:
存在一批组织成某种层次体系的对象
希望对这批对象或者其中一部分对象进行一个共同操作.
组合模式擅长于对大批对象进行操作.组织这类对象并把操作从一个层次向下一个层次传递,可以弱化对象间的耦合并且可以互换的使用一些类或者实例.
示例:表单验证
要求创建一个表单,可以保存,恢复,验证值.看似不复杂,但是主要问题在于表单中元素的内容和数目完全未知,而且因用户而异.紧密耦合到 Name 和 Address 特定表单域的 validate 函数不会起作用,因为无法验证哪些域...
现在是组合模式大展身手的时候,首先,我们要逐个验证表单的各个组成元素,判断属于组合对象还是叶对象.表单最基本的构成要素是用户用于输入数据的域,由input, select,textarea 等等组成.上面一层是用于组织域的 fieldset 标签.最顶层是表单自身.
叶对象 (input) --| |
-- 组合对象 (filedset) |
叶对象 (input) --| |
|=组合对象 (form)
叶对象 (select) --| |
--组合对象 (fieldset) |
叶对象 (textarea)--| |
组合对象和其所有子对象都具有相同的接口,可能有人会把他们看成超类和子类的关系,但是并非如此,叶对象没有继承上一级组合对象.
首先是创建一个动态表单并且实现save 和 validate 操作.表单中实际拥有的域因用户而异,所以 save,validate 函数不可能满足每个人需要,不过可以设计一个模块化表单,以便将来任何时候都能为其添加各种元素,不用修改 save 和 validate 函数.
不用为表单元素的每一种可能组合编写一套方法,应该让这两个方法和表单域自身关联起来.让每个域都可以保存和验证自己:
nameFieldset.validate();
nameFieldset.save();
难点在于如何同时在所有域上执行这些操作,使用组合模式来简化代码:要保存所有域,只需一次调用:topForm.save();
topForm 对象将会在所有子对象上递归调用 save 方法,实际的 save 操作只会发生在底层的叶对象上.组合对象志气到了一个传递调用的作用.
实现代码:
首先,创建组合对象和叶对象需要实现的两个接口:
var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
var FormItem = new Interface('FormItem', ['save']);
目前 FormItem 只要求实现一个 save 函数,稍后会对其进行扩充.
CompositeForm 类的代码如下:
var CompositeForm = function(id, method, action) { // implements Composite, Formitem
this.formComponents = [];
this.element = document.createElement('form');
this.element.id = id;
this.element.method = method || 'POST';
this.element.action = action || '#';
};
CompositeForm.prototype.add = function (child) {
Interface.ensureImplements(child, Composite, FormItem);
this.formComponents.push(child);
this.element.appendChild(child.getElement());
};
CompositeForm.prototype.remove = function (child) {
for (var i = 0, len = this.formComponents.length; i < len; i++) {
if (this.formComponents[i] === child) {
this.formComponents.splice(i, 1); // Remove one element from the array at position i.
break;
}
}
};
CompositeForm.prototype.getChild = function (i) {
return this.formComponents[i];
};
CompositeForm.prototype.remove = function (child) {
for (var i = 0, len = this.formComponents.length; i < len; i++) {
this.formComponents[i].save();
}
};
CompositeForm.prototype.getElement = function () {
return this.element;
};
CompositeForm 的子对象保存在一个数组中.使用 Interface.ensureImplements 是为了保证要添加到组合对象中的对象实现了正确的接口.
现在看看叶对象类:
var Field = function (id) { // implements Composite, FormItem
this.id = id;
};
Field.prototype.add = function () {};
Field.prototype.remove = function () {};
Field.prototype.getChild = function () {};
Field.prototype.save = function () {
setCookie(this.id, this.getValue());
};
Field.prototype.getElement = function () {
return this.element;
};
Field.prototype.getValue = function () {
throw new Error('Unsupported operation on the class Field.');
};
这个类将被各个叶对象类继承.它将 Composite 接口中的方法实现为空函数,因为叶节点不会有子对象.
这里最简单地实现了 save 方法.但是把用户原始数据放在 Cookie 是一个非常糟糕的做法.因为 Cookie 很容易被篡改,所以数据的有效性得不到保证.其次,存储在 Cookie 中的数据有大小限制.所以用户的数据可能不会被全部保存下来.
最后,还会影响性能,因为每次请求中 Cookie 都会作为 http 头被一起发送.
save 方法用 getValue 方法获得所要保存的对象值,getValue() 方法各个子类中的实现各不相同.使用 save 方法,不用提交表单也能保存表单的内容.
这个对于长表单来说很有用,因为用户可以不用填完表单中途保存, 然后忙完其他事情再来完成表单的填写:
var InputField = function(id, label) { // implements Composite, FormItem
Field.call(this, id);
this.input = document.createElement('input');
this.input.id = id;
this.label = document.createElement('label');
var labelTextNode = document.createTextNode(label);
this.label.appendChild(lavelTExtNode);
this.element = document.createElement('div');
this.element.className = 'input-field';
this.label.appendChild(this.label);
this.label.appendChild(this.input);
};
extend(InputField, Field); // Inherit from Field.
InputField.prototype.getValue = function () {
return this.input.value;
};
InputField 是 Field 的子类之一.大多数方法都是从Field 继承而来.但是他也实现了针对 input 标签的 getValue 方法的代码.TextareaField 和 SelectField 也实现了自己特有的 getValue 方法.
var TextareaField = function(id, label) { // implements Composite, FormItem
Field.call(this, id);
this.textarea = document.createElement('textarea');
this.textarea.id = id;
this.label = document.createElement('label');
var labelTextNode = document.createElement('select');
this.select.id = id;
this.element = document.createElement('div');
this.element.className = 'input-field';
this.element.appendChild(this.label);
this.element.appendChild(this.select);
};
extend(SelectField, Field); // Inherit from Field.
SelectField.prototype.getValue = function () {
return this.select.options[this.select.selectedIndex].value;
};
利
使用组合模式,简单的操作也能产生复杂的结果.不必编写大量手工遍历数据或者其他数据结构的粘合代码,只需对最顶层的对象执行操作,让每一个子对象自己传递这个操作.
组合模式中,各个对象之间的耦合非常松散.只要他们实现了同样的接口,那么改变他们的位置或者互相交换很简单.促进了代码得宠用,也有利于代码重构.
用组合模式组织起来的对象形成了一个出色的层次体系.每当对顶层对象执行一个操作时,实际上是在对整个结构进行深度优先搜索以查找节点.在该层次体系中添加,删除,查找节点都很容易.
弊
组合对象的易用性可能掩盖了它所支持的每一种操作的代价.由于对组合对象调用的任何操作都会被传递到他的所有子对象,那么如果层次体系很大,系统的性能就会受到影响.topGallery.show()
这样一个方法的调用会引起一次对整个数结构的遍历.
小结
它将一批子对象组织为树形结构,只要一条命令就可以操作树中的所有对象.他提高了代码的模块化程度,而且便于代码重构和对象的替换.这种模式特别适用于动态的 HTML 用户界面,在他的帮助下,可以在不知道用户界面的最终格局的情况下进行开发.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。